package com.im;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

// 服务器
class Server extends ServerSocket implements Runnable {
    private Connection mDatabase;                   // 数据库
    private final Map<String, Client> mOnlineUsers; // 在线用户列表

    /**
     * 构造方法。
     */
    public Server() throws IOException {
        super();

        // 初始化成员
        mOnlineUsers = new HashMap<>();

        // 创建数据库
        this.createDatabase();
    }

    /**
     * 析构方法。
     */
    @Override
    protected void finalize() {
        try {
            // 关闭套接字
            super.close();

            // 关闭数据库
            mDatabase.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 清空在线用户列表
        mOnlineUsers.clear();
    }

    /**
     * 创建数据库。
     */
    private void createDatabase() {
        try {
            // 打开数据库
            Class.forName("org.sqlite.JDBC");
            mDatabase = DriverManager.getConnection(Const.DB_PATH);

            // 创建表
            final Statement stat = mDatabase.createStatement();
            String sql;

            /* 用户表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY,%s TEXT,%s INTEGER)", 
                Const.TABLE_USERS, Const.FIELD_NAME, Const.FIELD_PASSWORD, Const.FIELD_ICON_INDEX);
            stat.executeUpdate(sql);

            /* 好友表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT)", 
                Const.TABLE_FRIENDS, Const.FIELD_OWNER, Const.FIELD_FRIEND_NAME);
            stat.executeUpdate(sql);

            /* 离线聊天表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT,%s TEXT,%s TEXT)", 
                Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, Const.FIELD_SRC_USER, Const.FIELD_CONTENT, Const.FIELD_TIME);
            stat.executeUpdate(sql);

            /* 离线添加好友表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT,%s INTEGER)", 
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, Const.FIELD_DST_USER, Const.FIELD_RESULT);
            stat.executeUpdate(sql);

            stat.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理添加好友请求。
     * @param request   添加好友请求
     * @param client    客户端
     */
    private void handleAddFriendRequest(AddFriendRequest request, Client client) {
        try {
            final Statement stat = mDatabase.createStatement();
            String sql;
            AddFriendResult result;

            // 检查目标用户是否存在
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_USERS, Const.FIELD_NAME, request.mDstUsername);
            ResultSet query = stat.executeQuery(sql);
            if (!query.next()) {
                // 查无此人，通知源用户
                result = new AddFriendResult(0, request.mDstUsername, ResultCode.USER_NOT_FOUND, "");
                client.sendData(result);
                stat.close();
                return;
            }
            query.close();

            // 检查是否已经是好友了
            sql = String.format("SELECT * FROM %s WHERE %s='%s' AND %s='%s'", Const.TABLE_FRIENDS, 
                Const.FIELD_OWNER, request.mSrcUsername, Const.FIELD_FRIEND_NAME, request.mDstUsername);
            query = stat.executeQuery(sql);
            if (query.next()) {
                // 已经是好友了，通知源用户
                result = new AddFriendResult(0, request.mDstUsername, ResultCode.ALREADY_BE_FRIENDS, "");
                client.sendData(result);
                stat.close();
                return;
            }
            query.close();

            // 如果目标用户不在线，则将请求信息插入到数据库中
            final Client friendUser = mOnlineUsers.get(request.mDstUsername);
            if (friendUser == null) {
                sql = String.format("INSERT INTO %s (%s,%s) VALUES('%s','%s')", 
                    Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, Const.FIELD_DST_USER, request.mSrcUsername, request.mDstUsername);
                stat.execute(sql);
                stat.close();
                return;
            }
            stat.close();

            // 向目标用户发送请求
            friendUser.sendData(request);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理添加好友结果。
     * @param result    添加好友结果
     */
    private void handleAddFriendResult(AddFriendResult result) {
        try {
            final Statement stat = mDatabase.createStatement();
            String sql;

            // 如果结果是同意，则将相关信息写到好友表中
            if (result.mResultCode == ResultCode.ADD_FRIEND_ALLOW) {
                sql = String.format("INSERT INTO %s VALUES('%s','%s')", Const.TABLE_FRIENDS, result.mSrcUsername, result.mDstUsername);
                stat.execute(sql);

                sql = String.format("INSERT INTO %s VALUES('%s','%s')",  Const.TABLE_FRIENDS, result.mDstUsername, result.mSrcUsername);
                stat.execute(sql);
            }

            // 如果目标用户不在线，则将结果信息插入到数据库中
            final Client friendUser = mOnlineUsers.get(result.mSrcUsername);
            if (friendUser == null) {
                sql = String.format("INSERT INTO %s VALUES('%s','%s','%d')", 
                    Const.TABLE_OFFLINE_ADD_FRIEND, result.mSrcUsername, result.mDstUsername, result.mResultCode.ordinal());
                stat.execute(sql);
                stat.close();
                return;
            }
            stat.close();

            // 向目标用户发送结果
            friendUser.sendData(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理聊天。
     * @param chatInfo  聊天信息
     */
    private void handleChat(ChatInfo chatInfo) {
        try {
            // 处理群发消息
            if (chatInfo.mDstUsername.equals(ImProtocol.MULTI_CHAT)) {
                for (HashMap.Entry<String, Client> entry: mOnlineUsers.entrySet()) {
                    // 跳过自己，向其它用户发送聊天信息
                    if (!entry.getKey().equals(chatInfo.mSrcUsername)) {
                        entry.getValue().sendData(chatInfo);
                    }
                }
                return;
            }

            // 如果目标用户不在线，则将聊天信息插入到数据库中
            final Client friendUser = mOnlineUsers.get(chatInfo.mDstUsername);
            if (friendUser == null) {
                final Statement stat = mDatabase.createStatement();
                final String sql = String.format("INSERT INTO %s VALUES('%s','%s','%s','%s')", 
                    Const.TABLE_OFFLINE_CHAT, chatInfo.mDstUsername, chatInfo.mSrcUsername, chatInfo.mContent, chatInfo.mTime);
                stat.execute(sql);
                stat.close();
                return;
            }

            // 发送给目标用户
            friendUser.sendData(chatInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理离线请求。
     * @param request   离线请求
     */
    private void handleLogoff(LogoffRequest request) {
        // 将该用户从在线列表移除
        mOnlineUsers.remove(request.mUsername);

        // 广播该用户的离线请求
        for (HashMap.Entry<String, Client> entry: mOnlineUsers.entrySet()) {
            entry.getValue().sendData(request);
        }
    }

    /**
     * 处理登录请求。
     * @param request   登录请求
     * @param client    客户端
     */
    @SuppressWarnings("resource")
    private void handleLogon(LogonRequest request, Client client) {
        try {
            final LogonResult logonResult = new LogonResult();
            final Statement stat = mDatabase.createStatement();
            String sql;

            // 检查该用户是否已经登录
            final Client onlineUser = mOnlineUsers.get(request.mUserInfo.mUsername);
            if (onlineUser != null) {
                // 已经登录了
                stat.close();
                logonResult.mResultCode = ResultCode.USER_ALREADY_LOGON;
                client.sendData(logonResult);
                return;
            }

            // 从数据库中查找该用户
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_USERS, Const.FIELD_NAME, request.mUserInfo.mUsername);
            ResultSet query = stat.executeQuery(sql);
            if (!query.next()) {
                // 查无此人
                query.close();
                stat.close();
                logonResult.mResultCode = ResultCode.USER_NOT_FOUND;
                client.sendData(logonResult);
                return;
            }

            // 比对密码
            final String password = query.getString(Const.FIELD_PASSWORD);
            if (!password.equals(request.mUserInfo.mPassword)) {
                // 密码不正确
                query.close();
                stat.close();
                logonResult.mResultCode = ResultCode.PASSWORD_WRONG;
                client.sendData(logonResult);
                return;
            }

            // 登录成功，获取该用户的相关信息
            logonResult.mResultCode = ResultCode.LOGON_SUCCEEDED;
            logonResult.mIconIndex = query.getInt(Const.FIELD_ICON_INDEX);
            query.close();

            // 获取该用户的好友列表
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_FRIENDS, Const.FIELD_OWNER, request.mUserInfo.mUsername);
            query = stat.executeQuery(sql);

            int friendCount;
            final List<UserInfo> friendList = new LinkedList<>();
            String friendName;
            int iconIndex;
            final Statement iconIndexStat = mDatabase.createStatement();
            boolean isOnline;
            final List<Boolean> onlineList = new LinkedList<>();

            for (friendCount = 0; query.next(); friendCount++) {
                friendName = query.getString(Const.FIELD_FRIEND_NAME);

                sql = String.format("SELECT %s FROM %s WHERE %s='%s'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, friendName);
                final ResultSet iconIndexQuery = iconIndexStat.executeQuery(sql);
                iconIndexQuery.next();
                iconIndex = iconIndexQuery.getInt(Const.FIELD_ICON_INDEX);
                iconIndexQuery.close();

                final UserInfo userInfo = new UserInfo(iconIndex, null, friendName);
                friendList.add(userInfo);

                final Client friendUser = mOnlineUsers.get(friendName);
                isOnline = (friendUser != null);
                onlineList.add(isOnline);

                // 如果好友在线，则向好友发送通知
                if (isOnline) {
                    final FriendLogon friendLogon = new FriendLogon(request.mUserInfo.mUsername);
                    friendUser.sendData(friendLogon);
                }
            }
            query.close();
            logonResult.mFriendCount = friendCount;
            logonResult.mFriends = friendList;
            logonResult.mFriendsOnline = onlineList;

            // 发送登录结果
            client.sendData(logonResult);

            // 查询是否有和该用户相关的离线添加好友请求，有则发送
            sql = String.format("SELECT * FROM %s WHERE %s='%s' AND %s IS NULL", 
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_DST_USER, request.mUserInfo.mUsername, Const.FIELD_RESULT);
            query = stat.executeQuery(sql);
            String srcUsername;
            while (query.next()) {
                srcUsername = query.getString(Const.FIELD_SRC_USER);
                sql = String.format("SELECT %s FROM %s WHERE %s='%s'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, srcUsername);
                final ResultSet iconIndexQuery = iconIndexStat.executeQuery(sql);
                iconIndexQuery.next();
                final AddFriendRequest addFriendRequest = new AddFriendRequest(request.mUserInfo.mUsername, 
                    iconIndexQuery.getInt(Const.FIELD_ICON_INDEX), srcUsername);
                iconIndexQuery.close();
                client.sendData(addFriendRequest);
            }
            query.close();

            // 删除数据库中的离线添加好友请求
            sql = String.format("DELETE FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_DST_USER, request.mUserInfo.mUsername);
            stat.execute(sql);

            // 查询是否有和该用户相关的离线添加好友结果，有则发送
            sql = String.format("SELECT * FROM %s WHERE %s='%s' AND %s IS NOT NULL", 
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, request.mUserInfo.mUsername, Const.FIELD_RESULT);
            query = stat.executeQuery(sql);
            String dstUsername;
            while (query.next()) {
                dstUsername = query.getString(Const.FIELD_DST_USER);
                final ResultCode result = ResultCode.values()[query.getInt(Const.FIELD_RESULT)];
                final AddFriendResult addFriendResult = new AddFriendResult(-1, dstUsername, result, request.mUserInfo.mUsername);
                client.sendData(addFriendResult);
            }
            query.close();

            // 删除数据库中的离线添加好友结果
            sql = String.format("DELETE FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, request.mUserInfo.mUsername);
            stat.execute(sql);

            // 查询是否有和该用户相关的离线聊天消息，有则发送
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, request.mUserInfo.mUsername);
            query = stat.executeQuery(sql);
            while (query.next()) {
                srcUsername = query.getString(Const.FIELD_SRC_USER);
                sql = String.format("SELECT %s FROM %s WHERE %s='%s'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, srcUsername);
                final ResultSet iconIndexQuery = iconIndexStat.executeQuery(sql);
                iconIndexQuery.next();
                final int srcIconIndex = iconIndexQuery.getInt(Const.FIELD_ICON_INDEX);
                iconIndexQuery.close();
                final String content = query.getString(Const.FIELD_CONTENT);
                final String time = query.getString(Const.FIELD_TIME);
                final ChatInfo chatInfo = new ChatInfo(content, request.mUserInfo.mUsername, srcIconIndex, srcUsername, time);
                client.sendData(chatInfo);
            }
            iconIndexStat.close();
            query.close();

            // 删除数据库中的离线聊天信息
            sql = String.format("DELETE FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, request.mUserInfo.mUsername);
            stat.execute(sql);
            stat.close();

            // 将该用户加入在线列表
            mOnlineUsers.put(request.mUserInfo.mUsername, client);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理注册请求。
     * @param request   注册请求
     * @param client    客户端
     */
    private void handleReg(RegRequest request, Client client) {
        final RegResult regResult = new RegResult();
        Statement stat = null;
        try {
            stat = mDatabase.createStatement();

            // 将该用户信息插入到数据库中
            final String sql = String.format("INSERT INTO %s VALUES('%s','%s','%d')", Const.TABLE_USERS, 
                request.mUserInfo.mUsername, request.mUserInfo.mPassword, request.mUserInfo.mIconIndex);
            stat.executeUpdate(sql);

            // 发送结果
            regResult.mResultCode = ResultCode.REG_SUCCEEDED;
            client.sendData(regResult);

            // 将该用户加入到在线列表中
            mOnlineUsers.put(request.mUserInfo.mUsername, client);
            stat.close();
        } catch (Exception e) {
            // 该用户已存在
            regResult.mResultCode = ResultCode.USER_EXISTS;
            try {
                client.sendData(regResult);
                if (stat != null) {
                    stat.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
    }

    /**
     * 处理客户端发送来的数据。
     * @param pro       协议
     * @param client    客户端
     */
    public synchronized void onGetMessageFromClient(ImProtocol pro, Client client) {
        switch (pro.mType) {
            case ADD_FRIEND_REQUEST:
                this.handleAddFriendRequest((AddFriendRequest)pro, client);
                break;
            case ADD_FRIEND_RESULT:
                this.handleAddFriendResult((AddFriendResult)pro);
                break;
            case CHAT:
                this.handleChat((ChatInfo)pro);
                break;
            case LOGOFF:
                this.handleLogoff((LogoffRequest)pro);
                break;
            case LOGON:
                this.handleLogon((LogonRequest)pro, client);
                break;
            case REG:
                this.handleReg((RegRequest)pro, client);
                break;
            default:
                break;
        }
    }

    /**
     * 接受客户端连接的线程。
     */
    @Override
    public void run() {
        Socket clientSocket;
        while (!super.isClosed()) {
            try {
                clientSocket = super.accept();
                new Client(this, clientSocket).start();
            } catch (Exception e) {
                break;
            }
        }
    }

    /**
     * 开启服务。
     * @return 成功则返回 true，否则返回 false
     */
    public boolean start() {
        // 绑定 IP 地址和端口号
        try {
            final InetAddress inetAddress = InetAddress.getByName(ImProtocol.SERVER_IP);
            final SocketAddress socketAddress = new InetSocketAddress(inetAddress, ImProtocol.SERVER_PORT);
            super.bind(socketAddress);
        } catch (Exception e) {
            return false;
        }

        // 开启接收数据的线程
        new Thread(this).start();

        return true;
    }
}
